package com.emotion.recognition.server.mlp;

import java.util.Collections;
import java.util.List;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

/**
 * An implementation of perceptron. <br>
 * 
 * Values get received by {@link #addInput(double)} => fills the {@link #inBuffer} => when inBuffer
 * is full, activation function {@link #f} is triggered => fire output through
 * {@link #outgoingEdges}
 * 
 * @author Minsang
 */
public class Perceptron extends AbstractNode {

    private final List<Edge> incomingEdges = Lists.newArrayList();

    private final List<Double> inBuffer = Lists.newArrayList();

    private final ActivationFunction f;

    private double lastTotalInput;

    private double errorSignal;

    public static Perceptron create(ActivationFunction f) {
        return new Perceptron(f);
    }

    private Perceptron(ActivationFunction f) {
        Preconditions.checkNotNull(f);
        this.f = f;
    }
    
    /**
     * Only an {@link Edge#propagate(double)} is allowed to call this.<br>
     * 
     * Provide an input value. When input has been received from all incominge edges, this
     * perceptron will activate, which in turn will pass its output to the next perceptrons.
     */
    public void provideInput(Edge source, double value) {
        inBuffer.add(value);

        if (inBuffer.size() == incomingEdges.size()) {
            // All incoming edges has provided an input, so activate this perceptron.
            double o = activate(inBuffer);

            // Reset the incoming buffer
            inBuffer.clear();
            
            // Propagate to outgoing edges
            fire(o);
        }
    }
    
    /**
     * Return (unweighted) output value o, given a list of input (weighted) values x_1w_1...x_n*w_n.
     */
    private double activate(List<Double> xs) {
        double sum = 0.0d;
        for (double d : xs) {
            sum += d;
        }
        lastTotalInput = sum;

        return f.apply(sum);
    }

    /**
     * Add an incoming edge. Do not call this method. Should be only called by
     * {@link Edge#connect(Perceptron, Perceptron)}.
     */
    public void addIncomingEdge(Edge incoming) {
        assert incoming.getDestination() == this;
        incomingEdges.add(incoming);
    }

    @Override
    public List<Edge> getIncomingEdges() {
        return Collections.unmodifiableList(incomingEdges);
    }

    public ActivationFunction getActivationFunction() {
        return f;
    }

    public double getLastTotalInput() {
        return lastTotalInput;
    }

    public double getErrorSignal() {
        return errorSignal;
    }

    public void setErrorSignal(double errorSignal) {
        this.errorSignal = errorSignal;
    }
}
